﻿using System;
using System.IO;

namespace Gonte.Text
{
    /// <summary>
    /// Breaks a stream into tokens and keeps the current row and column count on each operation
    /// </summary>
    public class TextTokenizer
        : IDisposable
    {
        #region Constructors

        public TextTokenizer(TextReader reader, ITokenizerHandler handler)
        {
            ExceptionThrower.ThrowWhenNull("reader", reader);
            ExceptionThrower.ThrowWhenNull("handler", handler);

            _reader = reader;
            _handler = handler;
        } 

        #endregion

        #region Methods

        public void Process()
        {
            _position = 0;
            _line = 1;
            _column = 0;

            while (Read());
        }

        public bool Read()
        {
            int intChar = _reader.Read();

            if (-1 == intChar)
            {
                return false;
            }

            UpdateCurrentCharacterLocation(intChar);

            // Call the handler
            _handler.OnCharacterRead(this);

            return true;
        }

        #endregion

        #region Properties

        /// <summary>
        /// The current character read by the tokenizer
        /// </summary>
        public char Character
        {
            get { return _character; }
        }

        /// <summary>
        /// The position of the current character being read
        /// </summary>
        public int Position
        {
            get { return _position; }
        }

        /// <summary>
        /// The line of the current character being read
        /// </summary>
        public int Line
        {
            get { return _line; }
        } 

        /// <summary>
        /// The column of the current character being read
        /// </summary>
        public int Column
        {
            get { return _column; }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            _reader.Dispose();
        }

        #endregion

        #region Helpers

        private void UpdateCurrentCharacterLocation(int intChar)
        {
            // Convert to a char
            _character = Convert.ToChar(intChar);

            ++_position;

            if (_character == '\n')
            {
                _column = 0; // Reset the column count
                ++_line;
            }
            else if (_character == '\r')
            {
                // Do not update lines or columns. We consider that as a part of the new line char "\r\n"
            }
            else
            {
                ++_column;
            }
        } 

        #endregion

        #region Fields

        private TextReader _reader;
        private ITokenizerHandler _handler;
        private char _character;

        // Location counters
        private int _position;
        private int _line;
        private int _column; 

        #endregion
    }
}
